/*******************************************************************************************
 * Copyright (c) 2009 Alex Robson, Rob Simmons, Craig Israel, and Rachel Twyford
 *
 * Use of this software in any form requires that you have both 
 * read and agreed to the following terms:
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions: 
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, 
 * this list of conditions and the following disclaimer in the 
 * documentation and/or other materials provided with the distribution.
 * Neither the name of Nvigorate nor the names of its contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ****************************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Xml.Serialization;
using Nvigorate.Common;
using Nvigorate.Relational.Mapping.Interface;
using Nvigorate.Xml;

namespace Nvigorate.Relational.Mapping
{
    [Serializable]
    public abstract class SourceMapBase : ISourceMap, IXmlSerializable
    {
        private readonly string _targetName = "";

        #region Implementation of ISourceMap

        protected List<IAssignment> _assignments = new List<IAssignment>();

        protected bool _canDelete = true;

        protected bool _canInsert = true;

        protected bool _canSelect = true;

        protected bool _canUpdate = true;

        protected int _createOrder;

        protected string _sourceName = "";


        private bool _targetRequiresConstructor;

        /// <summary>
        /// The collection of assignments which describe the bindings
        /// and how each one works
        /// </summary>
        public List<IAssignment> Assignments
        {
            get { return _assignments; }
            set { _assignments = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the delete operation is available 
        /// for the mapped type.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance can delete; otherwise, <c>false</c>.
        /// </value>
        public bool CanDelete
        {
            get { return _canDelete; }
            set { _canDelete = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether insert operation is available 
        /// for the mapped type.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance can insert; otherwise, <c>false</c>.
        /// </value>
        public bool CanInsert
        {
            get { return _canInsert; }
            set { _canInsert = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the select operation is available 
        /// for the mapped type.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance can select; otherwise, <c>false</c>.
        /// </value>
        public bool CanSelect
        {
            get { return _canSelect; }
            set { _canSelect = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the update operation is available 
        /// for the mapped type.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance can update; otherwise, <c>false</c>.
        /// </value>
        public bool CanUpdate
        {
            get { return _canUpdate; }
            set { _canUpdate = value; }
        }

        /// <summary>
        /// If set, specificies the order in which persistence to sources should take place
        /// </summary>
        public int CreateOrder
        {
            get { return _createOrder; }
            set { _createOrder = value; }
        }

        public IJoin Join { get; set; }

        /// <summary>
        /// The name of the source entity
        /// </summary>
        public string SourceName
        {
            get { return _sourceName; }
            set { _sourceName = value; }
        }

        /// <summary>
        /// The type of source represented by the Source Map
        /// </summary>
        public SourceMapType SourceType { get; set; }

        public bool TargetRequiresConstructor
        {
            get { return _targetRequiresConstructor; }
            set { _targetRequiresConstructor = value; }
        }

        #endregion

        #region Implementation of IXmlSerializable

        /// <summary>
        /// This method is reserved and should not be used. When implementing the IXmlSerializable interface, you should return null (Nothing in Visual Basic) from this method, and instead, if specifying a custom schema is required, apply the <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute" /> to the class.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Xml.Schema.XmlSchema" /> that describes the XML representation of the object that is produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)" /> method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)" /> method.
        /// </returns>
        public XmlSchema GetSchema()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Generates an object from its XML representation.
        /// </summary>
        /// <param name="reader">The <see cref="T:System.Xml.XmlReader" /> stream from which the object is deserialized. </param>
        public void ReadXml(XmlReader reader)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Converts an object into its XML representation.
        /// </summary>
        /// <param name="writer">The <see cref="T:System.Xml.XmlWriter" /> stream to which the object is serialized. </param>
        public void WriteXml(XmlWriter writer)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region Implementation of ILinqToXml

        private string GetForbiddenValue()
        {
            DelimitedBuilder forbidden = new DelimitedBuilder(",");
            if (!CanInsert)
                forbidden.Append("insert");
            if (!CanUpdate)
                forbidden.Append("update");
            if (!CanDelete)
                forbidden.Append("delete");
            if (!CanSelect)
                forbidden.Append("select");

            return forbidden.ToString();
        }

        private void ParseForbiddenValue(string forbidden)
        {
            CanInsert = !forbidden.Contains("insert");
            CanUpdate = !forbidden.Contains("update");
            CanDelete = !forbidden.Contains("delete");
            CanSelect = !forbidden.Contains("select");
        }

        public XElement GetXElementRepresentation()
        {
            var element = new XElement("source",
                                       new XAttribute("name", _sourceName),
                                       new XAttribute("persistOrder", _createOrder),
                                       new XAttribute("forbidden", GetForbiddenValue()),
                                       new XAttribute("type", SourceType.ToString()),
                                       _assignments.Select(a => GetAssignmentXElement(a)),
                                       GetJoinXElement()
                );
            return element;
        }

        public object ParseXElement(XElement element)
        {
            _sourceName = element.GetAttributeValue("name") ?? "";
            _createOrder = int.Parse(element.GetAttributeValue("persistOrder") ?? "0");
            ParseForbiddenValue(element.GetAttributeValue("forbidden") ?? "");

            SetSourceType(element.GetAttributeValue("type"));
            _assignments.AddRange(element.Elements("assignment").Select(e => ParseAssignment(e)));
            var first = element.Descendants("relates").FirstOrDefault();
            if (first != null)
            {
                Join = ParseJoin(first);
            }
            return this;
        }

        private void SetSourceType(string sourceType)
        {
            if (string.IsNullOrEmpty(sourceType) || !Enum.IsDefined(typeof(SourceMapType), sourceType))
            {
                SourceType = SourceMapType.Parent;
            }
            else
            {
                SourceType = (SourceMapType)Enum.Parse(typeof (SourceMapType), sourceType);
            }
        }

        #endregion

        #region Constructors

        public SourceMapBase(string targetName)
        {
            _targetName = targetName;
        }

        #endregion

        #region Protected Methods

        protected virtual IAssignment ParseAssignment(XElement element)
        {
            var m = new MemberAssignment(_sourceName, _targetName);
            m.ParseXElement(element);
            return m;
        }

        protected virtual IJoin ParseJoin(XElement element)
        {
            var join = new LeftJoin();
            join.ParseXElement(element);
            return join;
        }

        protected virtual XElement GetAssignmentXElement(IAssignment assignment)
        {
            return assignment.GetXElementRepresentation();
        }

        protected virtual XElement GetJoinXElement()
        {
            return Join == null ? null : Join.GetXElementRepresentation();
        }
        #endregion
    }
}